package net.intellect.collectpro.gestores;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.intellect.collectpro.objects.Empresa;
import net.intellect.collectpro.util.Constantes;
import net.intellect.force.UtilBasic;

import com.sforce.soap.enterprise.ID;
import com.sforce.soap.enterprise.sobject.Cobro__c;
import com.sforce.soap.enterprise.sobject.Conf_Empresa__c;
import com.sforce.soap.enterprise.sobject.Pago__c;
import com.sforce.soap.enterprise.sobject.SObject;

/**
 * Last change: Agregar logica para actualizar los pagos de solo una empresa, o de una sola
 * segun se indique por parametros. 
 * @author Intellect International (TRF)
 */
public class Pago extends GestorSalesforce {
	protected final static String campoFechaRehabilitacion = "Fecha_Rehabilitacion__c";
	private Map<String,String> metodosPago = new HashMap<String,String>();

	/**
	 * Constructor. 
	 * @param user Sales force valid user name
	 * @param password Sales force valid password for the user defined
	 * @param token Salesforce valid security token corresponded to the user defined
	 * @param codEmpresa Company id
	 * @param isSandBox Boolean flag to let us know if we should use Sandbox environment or production
	 */
	public Pago(String user, String password, String token, String codEmpresa, boolean isSandBox,String urlProxy,String puertoProxy) {
		super(user, password, token, codEmpresa,isSandBox,urlProxy,puertoProxy);
		initializePaymentMethods();
	}	

	/**
	 * Constructor. 
	 * @param user Salesforce valid user name
	 * @param password Salesforce valid password for the user defined
	 * @param token Salesforce valid security token corresponded to the user defined
	 * @param codEmpresa Company id
	 */
	public Pago(String user, String password, String token, String codEmpresa,String urlProxy,String puertoProxy) {
		this(user, password, token, codEmpresa, false,urlProxy,puertoProxy);
		initializePaymentMethods();
	}

	/**
	 * Maps the name of a payments handler, against the method name.
	 * Please add here a row in case another company is included into this program.
	 */
	private void initializePaymentMethods() {
		metodosPago.put(Constantes.METODO_PAGO_BANCO, "net.intellect.collectpro.gestores.GestorBanco");
		metodosPago.put(Constantes.METODO_PAGO_TARJETAS, "net.intellect.collectpro.gestores.GestorPagosTarjetas");
	}

	/**
	 * Processes the payments for a specific date
	 * @param fechaProceso Date from which the payments should be processed. 
	 * @throws Exception
	 */
	public void procesarPagosDia(Date fecha) throws Exception {
		try {		
			
			if (codEmpresa != null) {
				
				if ("todos".compareToIgnoreCase(codEmpresa) == 0) {
					aplicarTodosPagos(fecha);

				} else {
					Empresa empresa = this.obtenerConfiguracionEmpresa(codEmpresa);
					this.aplicarPagosDeEmpresa(empresa, fecha);
				}
			} else {
				logger.error("Parametro de empresa invalido");
			}
			
			
		} catch (Exception e) {
			logger.error(e);
			throw e;
		}		
	}
	
	/**
	 * Create the instance of the payment class depending on the company information.
	 * @param empresa
	 * @param pagos
	 */
	private void aplicarPagosDeEmpresa(Empresa empresa, Date fecha) {
		try {			
			// Executes class construction by reflection
			Class implementacionPagos = Class.forName(metodosPago.get(empresa.getMetodoPago()));
			Class[] argumentosConstDef = new Class[] { String.class, String.class, String.class, String.class, boolean.class,String.class,String.class };
			Object[] argumentosConst = new Object[] { this.user, this.password, this.token, this.codEmpresa, this.isSandBox,this.urlProxy,this.puertoProxy};
		    Constructor c = implementacionPagos.getConstructor(argumentosConstDef);
		    GestorDePagos newInstance = (GestorDePagos) c.newInstance(argumentosConst);
			newInstance.aplicarPagos(empresa.getId(), fecha);
		    
		} catch(Exception e) {
			logger.error("Error al invocar metodos por reflexion de la configuracion de pago: " + 
				empresa.getMetodoPago() + " :" + e);
		}
	}
	
	
	/**
	 * Processes all the payments (for all the companies) for a specific date
	 * @param fecha 
	 * @throws Exception
	 */
	public void aplicarTodosPagos(Date fecha) throws Exception {
		List<Empresa> empresas = this.getEmpresas();
		
		// runs every company
		for (int i = 0; i < empresas.size(); i++) {
			Empresa empresa = empresas.get(i);
			try {
				this.aplicarPagosDeEmpresa(empresa, fecha);
			} catch(Exception e) {
				logger.error("Error al invocar metodos por reflexion de la clase de pago " + 
						empresa.getMetodoPago() + " :" + e);
			}
		}
		
	}

	/**
	 * Obtiene una lista de cobros a partir de un mapa de pagos
	 * @param mapPagosPositivos
	 * @return
	 * @throws Exception
	 */
	public List<Cobro__c> obtenerCobrosXPagos(String estadoCobro,Map<String, Integer> mapPagos) throws Exception{
		List<Cobro__c> cobros = null; 
		
		try {			
			String sql = 
					"Select " +
						"c.Id, " +
						"c.TC_Pago_Minimo_Local__c, " +
						"c.TC_Pago_Minimo_USD__c, " +
						"c.TC_Ciclo_Pertenece__c, " +
						"c.TC_Nivel_Mora_Local__c, " +
						"c.TC_Nivel_Mora_USD__c, " +
						"c.Tipo_Pago__c, " +
						"c.TC_Ciclo__c, " +
						"c.IsMarcadorPredictivo__c," +
						"c.OwnerId, " +
						"(Select Monto__c,CurrencyIsoCode From Pagos__r) " +
					"From Cobro__c c " +
					"Where " +
						"Estado_Cobro__c = '" + estadoCobro +"' and " + 
						"Id IN " ; 								
			List<SObject> objects = this.getObjectsByMapSalesforce(sql,mapPagos);
			
			// Verifies if the query returned any value to proceed to create the list of charges.
			if (objects!=null) {
				cobros = new ArrayList<Cobro__c>();
				for (SObject object:objects) {
					cobros.add((Cobro__c)object);
				}
			}	
			
		} catch (Exception e) {
			logger.error(e);
			throw e;
		}
		
		return cobros;
	}

	/**
	 * Queries payments which were uploaded on a specific date. This methods does not
	 * filter the payments by company, just by date.
	 * @param fecha Date when the payments were uploaded.
	 * @return List of payments which were uploaded on the specific date. Empty list if any
	 * payment were found.
	 * @throws Exception
	 */
	private List<Pago__c> obtenerPagos(Date fecha) throws Exception {
		List<Pago__c> pagos = null;

		try {			 			
			String sql = 
				"Select p.Numero_de_pago__c, p.Monto__c, p.Info_Cuenta__c, p.Id, p.Empresa_del_grupo__c, p.Cobro__c " +
				"From Pago__c p " +
				"Where p.CreatedDate >= " + UtilBasic.obtenerFechaSF(fecha);
			
			// Verifies if the query returned any value to proceed to create the list of payments.
			List<SObject> objects = this.getObjectsSalesforce(sql);
			if (objects!=null) {
				pagos = new ArrayList<Pago__c>();
				for (SObject object:objects) {
					pagos.add((Pago__c)object);
				}
			} 	
			
		} catch (Exception e) {
			logger.error(e);
			throw e;
		}
		return pagos;
	}

	/**
	 * Gets the list of payments from a specific company which were uploaded into the dated
	 * defined by parameter
	 * @param companyId Object ID of the company.
	 * @param date Date
	 * @return List of objects Pago__c, empty list if no payments were found. 
	 * @throws Exception
	 */
	protected List<Pago__c> obtenerPagos(ID companyId, Date date) throws Exception {
		List<Pago__c> pagos = null;
		
		try {			
			String sql = 
				"Select p.Numero_de_pago__c, p.Monto__c, p.Info_Cuenta__c, p.Id, p.Empresa_del_grupo__c, p.Cobro__c " +									
				"From Pago__c p " +
				"Where " +
					"p.Cobro__c != '' and " +
					"p.Empresa_del_grupo__c ='" + companyId + "' and " +
					"p.CreatedDate >= " + UtilBasic.obtenerFechaSF(date);				
			List<SObject> objects = this.getObjectsSalesforce(sql);
			
			// Verifies if the query returned any value to proceed to create the list of payments.
			if (objects!=null) {
				pagos = new ArrayList<Pago__c>();
				for (SObject object:objects) {
					pagos.add((Pago__c)object);
				}
			} 
		} catch (Exception e) {
			logger.error(e);
			throw e;
		}

		return pagos;
	}

	/**
	 * Gets the list of payments from a specific company which were uploaded into the dated
	 * defined by parameter
	 * @param companyId Object ID of the company.
	 * @param date Date
	 * @return List of objects Pago__c, empty list if no payments were found. 
	 * @throws Exception
	 */
	protected List<Pago__c> obtenerPagosBanco(ID companyId, Date date) throws Exception {
		List<Pago__c> pagos = null;
		
		try {			
			String sql = 
				   "Select " +
				    "p.Id, " +
				    "p.Monto__c, " +       
				    "p.Cobro__c, " +
				    "p.Cobro__r.Saldo_exigible__c, " +
				    "p.Cobro__r.TC_Nivel_Mora_Consolidado_Real__c, " +
				    "p.Cobro__r.Estado_Cobro__c, " +
				    "p.Cobro__r.Para_rehabilitaci_n__c, " +
				    "p.Cobro__r.Para_Liberaci_n_4__c, " +
				    "p.Cobro__r.Para_Liberaci_n_3__c, " +
				    "p.Cobro__r.Para_Liberaci_n_2__c, " +
				    "p.Cobro__r.Para_Liberaci_n_1__c, " +
				    "p.Cobro__r.Para_Contenci_n__c " +
				   "From Pago__c p " +
				   "Where " +
				   "p.Empresa_del_grupo__c ='" + companyId + "' and " +
				   "p.CreatedDate >= " + UtilBasic.obtenerFechaSF(date) + " and " +
				   "p.Cobro__r.Cerrado__c = false and " + 
				   "p.Cobro__r.Estado_Cobro__c !='" + Constantes.COBRO_REHABILITADO + "'";
			
			
			List<SObject> objects = this.getObjectsSalesforce(sql);
			
			// Verifies if the query returned any value to proceed to create the list of payments.
			if (objects!=null) {
				pagos = new ArrayList<Pago__c>();
				for (SObject object:objects) {
					pagos.add((Pago__c)object);
				}
			} 
		} catch (Exception e) {
			logger.error(e);
			throw e;
		}

		return pagos;
	}
	/**
	 * Returns the configuration of all the companies of the payments
	 * @param pagos List of payments
	 * @return List of the companies's configuration
	 * @throws Exception
	 */
	private List<Empresa> getEmpresas() throws Exception{
		List<Empresa> empresas = new ArrayList<Empresa>();

		try {
			// Queries companies
			empresas = this.obtenerConfiguracionEmpresas();

		} catch (Exception e) {
			logger.error(e);
			throw e;
		}
		return empresas;
	}

	/**
	 * Groups payments by company
	 * @param pagos List of payments to be grouped
	 * @return Map where the key value corresponds to the company id, and the values are
	 * its payments
	 */
	private Map<ID, List<Pago__c>> getPagosMap(List<Pago__c> pagos) {
		Map<ID, List<Pago__c>> pagosMap = new HashMap<ID, List<Pago__c>>();
		
		for (int i = 0; i < pagos.size(); i++) {
			Pago__c pago = pagos.get(i);
			if (!pagosMap.containsKey(pago.getEmpresa_del_grupo__c())) {
				pagosMap.put(pago.getEmpresa_del_grupo__c(), new ArrayList<Pago__c>());
			}
			pagosMap.get(pago.getEmpresa_del_grupo__c()).add(pago);
		}
		return pagosMap;
	}

	/**
	 * Returns the information of companies
	 * @return List of objects Empresa
	 * @throws Exception
	 */
	public List<Empresa> obtenerConfiguracionEmpresas() throws Exception {
		List<Empresa> empresas = null; 
		try {
			 String sql = 
				 "Select " +
				 	"e.Id, e.Metodo_Pago__c, E.Name From Conf_Empresa__c e ";
			 List<SObject> objects = this.getObjectsSalesforce(sql);
		  
			// Verifies if the query returned any value to proceed to create the list of companies.
			 if (objects != null) {
				 empresas = new ArrayList<Empresa>();
				 for (SObject object:objects) {
					Conf_Empresa__c empresaObj = (Conf_Empresa__c) object;
					Empresa empresa = new Empresa();
					empresa.setName(empresaObj.getName());
					empresa.setMetodoPago(empresaObj.getMetodo_Pago__c());
					empresa.setId(empresaObj.getId());
					empresas.add(empresa);
				 }
			 }       
   
		 } catch (Exception e) {
			 logger.error(e);
			 throw e;
		 }
		 return empresas;
	}

	/**
	 * Returns the charges specified into the map
	 * @param cobrosId HashMap containing charges IDs into the key set value.
	 * @return List of requested charges.
	 * @throws Exception
	 */
	public List<Cobro__c> obtenerCobros(Map<String,Integer> cobrosId) throws Exception {
		List<Cobro__c> cobros = null;

		try {
			 String sql = 
					"Select " +
					"c.Id, " +
					"c.TC_Pago_Minimo_Local__c, " +
					"c.TC_Pago_Minimo_USD__c, " +
					"c.TC_Ciclo_Pertenece__c, " +
					"c.TC_Nivel_Mora_Local__c, " +
					"c.TC_Nivel_Mora_USD__c, " +
					"c.Tipo_Pago__c, " +
					"c.TC_Ciclo__c, " +
					"c.IsMarcadorPredictivo__c," +
					"c.OwnerId, " +
					"(Select Monto__c,CurrencyIsoCode From Pagos__r) " +
				"From Cobro__c c " +
				"Where Id IN " ;
			 List<SObject> objects = this.getObjectsByMapSalesforce(sql, cobrosId);
		  
			// Verifies if the query returned any value to proceed to create the list of charges.
			 if (objects!=null) {
				 cobros = new ArrayList<Cobro__c>();
				 for (SObject object:objects) {
					 cobros.add((Cobro__c)object);
				 }
			 }       
	   
		 } catch (Exception e) {
			 logger.error(e);
			 throw e;
		 }
		 return cobros;
	}
}
